from sklearn.metrics import r2_score,make_scorer
from sklearn.model_selection import train_test_split
import numpy as np
# 导入绘图库
import matplotlib.pyplot as plt
# 使用matplotlib在Jupyter Notebook中绘图时，需要使用这个
#%matplotlib inline
# 导入sklearn的清洗分割、学习曲线和决策树回归器的对象和函数
from sklearn.model_selection import ShuffleSplit, learning_curve,GridSearchCV
from sklearn.tree import DecisionTreeRegressor

#计算拟合程度，越接近1就越高
def performance_metric(y_true, y_predict):
    score = r2_score(y_true,y_predict)
    return score

#分割数据为训练数据和测试数据
# test_size 0.1为10%
# random_state设置值可以保证多次返回一致
# 返回x_train,x_test,y_train,_y_test
def split_data(x,y,test_size,random_state):
    return train_test_split(x,y,test_size=test_size,random_state=random_state)

# 定义模型的性能对比函数
# 通过不同大小的深度值来创建for循环里的模型，然后以图的形式展现
def ModelLearningGraphMetrics(X, y):
    # 清洗和分割数据对象定义
    # 参数1：n_splits表示重新清洗和分割数据的迭代次数，默认值为10
    # 参数2：test_size=0.2表示有0.2的数据用于测试，也就是20%的数据用于测试，80%的数据用于训练
    # 参数3：random_state表示随机数生成器的种子，如果希望第二次调用ShuffleSplit()方法
    #        的结果和第一次调用的结果一致，那么就可以设置一个值，多少都可以，生产环境不要设值
    cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
    # 生成训练集大小
    # 函数np.rint()是计算数组各元素的四舍五入的值到最近的整数
    # 函数np.linspace(start_i, stop_i, num)表示从起始值到结束值之间，以均匀的间隔返回指定个数的值。
    # 那么这里就是从1开始、以X结束的总行数的80%的数据，数据间隔是9，最后将数据元素都转换成整型
    train_sizes = np.rint(np.linspace(1, X.shape[0]*0.8 - 1, 9)).astype(int)
    # 创建一个绘图窗口，大小10×7，单位是英寸（inch）
    fig = plt.figure(figsize=(10, 7))
    # 根据深度值创建不同的模型
    # 这里的深度值就是1、3、6、10这4个
    for k, depth in enumerate([1,3,6,10]):
        # 根据深度(max_depth)值来创建决策树回归器
        regressor = DecisionTreeRegressor(max_depth=depth)
        # 通过学习曲线函数计算训练集和测试集的分值
        # 参数1：评估器，这里就是决策树回归器
        # 参数2：特征样本，房屋特征
        # 参数3：目标标签，房屋价格
        # 参数4：训练样本的个数，这是用来生成学习曲线的
        # 参数5：交叉验证生成器，或可迭代对象
        # 参数6：评分器，是一个可调用对象
        sizes, train_scores, test_scores = learning_curve(
           regressor, X, y, train_sizes=train_sizes, cv=cv, scoring='r2')
        # 计算训练集分值和测试集分值的标准差
        train_std = np.std(train_scores, axis=1)
        test_std = np.std(test_scores, axis=1)
        # 计算训练集分值和测试集分值的均值
        train_mean = np.mean(train_scores, axis=1)
        test_mean = np.mean(test_scores, axis=1)
        # 根据学习曲线值来绘制图，四个图的位置通过k+1来控制
        ax = fig.add_subplot(2, 2, k+1)
        # 绘制训练得分线，plot()方法
        #  参数1：X轴方向的值
        #  参数2：y轴方向的值
        #  参数3：绘制出来的线的样式风格，比如这里的“o”表示一个圆点标记，而“-”表示实线
        #  参数4：绘制的线的颜色
        #  参数5：图例上的标题
        ax.plot(sizes, train_mean, 'o-', color='r', label='Training Score')
        # 绘制测试得分线
        ax.plot(sizes, test_mean, 'o-', color='g', label='Testing Score')
        # fill_between()方法表示为训练得分线描边
        #  参数1：X轴方向的值
        #  参数2：y轴方向的覆盖下限
        #  参数3：y轴方向的覆盖上限
        #  参数4：设置覆盖区域的透明度
        #  参数5：设置覆盖区域的颜色
        ax.fill_between(sizes, train_mean - train_std,
            train_mean + train_std, alpha=0.15, color='r')
        # fill_between()方法表示为测试得分线描边
        ax.fill_between(sizes, test_mean - test_std,
            test_mean + test_std, alpha=0.15, color='g')
        # 在绘图的窗口上添加标题
        ax.set_title('max_depth = {}'.format(depth))
        # 设置X轴的标题
        ax.set_xlabel('Number of Training Points')
        # 设置y轴的标题
        ax.set_ylabel('Score')
        # 设置X轴方向的最小值和最大值
        ax.set_xlim([0, X.shape[0]*0.8])
        # 设置y轴方向的最小值和最大值
        ax.set_ylim([-0.05, 1.05])
    # 添加图例
    ax.legend(bbox_to_anchor=(1.05, 2.05), loc='lower left', borderaxespad=0.)
    # 添加图形总标题
    fig.suptitle('Decision Tree Regressor Learning Performances',
                 fontsize=16, y=1.03)
    # 自动调整subplot符合图的区域的参数的布局。生产环境中不要使用该函数，因为这是一个实验特性函数
    fig.tight_layout()
    # 显示绘图
    fig.show()

# 从sklearn库导入网格搜索VC、数据清洗与分割、决策树和分值计算对象的函数
def gridSearchVC_fit_model(X, y):
    # 清洗和分割数据对象定义，
    # 参数1：n_splits表示重新清洗和分割数据的迭代次数，默认值为10
    # 参数2：test_size=0.2表示有0.2的数据用于测试，也就是20%的数据用于测试，80%的数据用于训练
    # 参数3：random_state表示随机数生成器的种子，如果希望第二次调用ShuffleSplit()方法
    #        的结果和第一次调用的结果一致，那么就可以设置一个值，多少都可以，生产环境不要设值
    cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
    # 创建决策树回归器对象
    regressor = DecisionTreeRegressor(random_state=0)
    # 创建一个字典，表示max_depth的参数值是从1到10
    # 注意：如果代码运行的环境是Python 2，去掉这个list()函数调用
    params = { "max_depth" : list(range(1, 10)) }
    # 通过make_scorer()函数将上面定义的performance_metric()函数转换成计算分值函数
    scoring_fnc = make_scorer(score_func=performance_metric)
    # 创建网格搜索对象
    # 参数1：评估器，就是回归器，这里表示的是决策树回归器
    # 参数2：网格搜索参数
    # 参数3：计算分值函数
    # 参数4：cv（Cross-Validation）交叉验证，传入交叉验证生成器，或者可迭代对象
    grid = GridSearchCV(estimator=regressor, param_grid=params,
                         scoring=scoring_fnc, cv=cv)
    # 根据数据计算/训练适合网格搜索对象的最佳模型
    grid = grid.fit(X, y)
    # 返回计算得到的最佳模型
    return grid.best_estimator_

def PredictYResult(X,y,fitter):
    epochs = 10
    y_predict_test_price = None
    X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,random_state=0)

    for epochs in range(epochs):
        reg = fitter(X_train, y_train)
        predictec_price = reg.predict(X_test)
        y_predict_test_price = predictec_price

    return y_test,y_predict_test_price

#对比预测数据和测试数据图
def plotVersusFigure(y_true_price,y_predict_price):
    plt.figure(figsize=(10,7))
    X_show = np.rint(np.linspace(1,np.max(y_true_price),len(y_true_price))).astype(int)
    plt.plot(X_show,y_true_price,'o-',color='c')

    X_show_predicted = np.rint(np.linspace(1,np.max(y_predict_price),len(y_predict_price))).astype(int)
    plt.plot(X_show_predicted,y_predict_price,'o-',color='m')

    plt.legend(loc='lower right',labels=["Test Data", "Predicted Data"])
    plt.xlabel("Index")
    plt.ylabel("Result")
    plt.show()

    plt.title('Test And Predicted Result')